//
// Created by root on 5/26/17.
//

#include "runner_tspl.h"

static void test_section_2_1() {
    assert_string_equal(interpret(_STR("Hi Mom!")), "Hi Mom!");
    assert_string_equal(interpret(_STR("hello")), "hello");
    assert_string_equal(interpret(_INT("42")), "42");
    assert_string_equal(interpret(_FRAC("22", "7")), "22/7");
    assert_string_equal(interpret(_DEC("3.141592653")), "3.141592653");
    assert_string_equal(interpret("+"), "#<procedure>");
    assert_string_equal(interpret("(+ 76 31)"), "107");
    assert_string_equal(interpret("(* -12 10)"), "-120");
    assert_string_equal(interpret("'(a b c d)"), "(a b c d)");

    assert_string_equal(interpret("(car '(a b c))"), "a");
    assert_string_equal(interpret("(cdr '(a b c))"), "(b c)");
    assert_string_equal(interpret("(cons 'a '(b c))"), "(a b c)");
    assert_string_equal(interpret("(cons (car '(a b c)) (cdr '(d e f)))"), "(a e f)");

    assert_string_equal(interpret("(define square (lambda (n) (* n n)))"), "#<procedure>");
    assert_string_equal(interpret("(square 5)"), "25");
    assert_string_equal(interpret("(square -200)"), "40000");
    assert_string_equal(interpret("(square 0.5)"), "0.25");
    assert_string_equal(interpret("(square -1/2)"), "1/4");

    assert_string_equal(interpret("(define reciprocal (lambda (n) (if (= n 0) \"oops!\" (/ 1 n))))"), "#<procedure>");
    assert_string_equal(interpret("(reciprocal 10)"), "1/10");
    assert_string_equal(interpret("(reciprocal 1/10)"), "10");
    assert_string_equal(interpret("(reciprocal 0)"), "\"oops!\"");
    assert_string_equal(interpret("(reciprocal (reciprocal 1/10))"), "1/10");
}

static void test_section_2_2() {
    assert_string_equal(interpret("123456789987654321"), "123456789987654321");
    assert_string_equal(interpret("3/4"), "3/4");
    assert_string_equal(interpret("2.718281828"), "2.718281828");
    assert_string_equal(interpret("2.2+1.1i"), "2.2+1.1i");

    assert_string_equal(interpret("(+ 1/2 1/2)"), "1");
    assert_string_equal(interpret("(- 1.5 1/2)"), "1.0");
    assert_string_equal(interpret("(* 3 1/2)"), "3/2");
    assert_string_equal(interpret("(/ 1.5 3/4)"), "2.0");

    assert_string_equal(interpret("(+ (+ 2 2) (+ 2 2))"), "8");
    assert_string_equal(interpret("(- 2 (* 4 1/3))"), "2/3");
    assert_string_equal(interpret("(* 2 (* 2 (* 2 (* 2 2))))"), "32");
    assert_string_equal(interpret("(/ (* 6/7 7/2) (- 4.5 1.5))"), "1.0");

    assert_string_equal(interpret("(quote (1 2 3 4 5))"), "(1 2 3 4 5)");
    assert_string_equal(interpret("(quote (\"this\" \"is\" \"a\" \"list\"))"), "(\"this\" \"is\" \"a\" \"list\")");
    assert_string_equal(interpret("(quote (+ 3 4))"), "(+ 3 4)");

    assert_string_equal(interpret("'(1 2 3 4)"), "(1 2 3 4)");
    assert_string_equal(interpret("'((1 2) (3 4))"), "((1 2) (3 4))");
    assert_string_equal(interpret("'(/ (* 2 -1) 3)"), "(/ (* 2 -1) 3)");

    assert_string_equal(interpret("(quote hello)"), "hello");
    assert_string_equal(interpret("'2"), "2");
    assert_string_equal(interpret("'2/3"), "2/3");
    assert_string_equal(interpret("(quote \"Hi Mom!\")"), "\"Hi Mom!\"");

    assert_string_equal(interpret("(car '(a b c))"), "a");
    assert_string_equal(interpret("(cdr '(a b c))"), "(b c)");
    assert_string_equal(interpret("(cdr '(a))"), "()");
    assert_string_equal(interpret("(car (cdr '(a b c)))"), "b");
    assert_string_equal(interpret("(cdr (cdr '(a b c)))"), "(c)");
    assert_string_equal(interpret("(car '((a b) (c d)))"), "(a b)");
    assert_string_equal(interpret("(cdr '((a b) (c d)))"), "((c d))");

    assert_string_equal(interpret("(cons 'a '())"), "(a)");
    assert_string_equal(interpret("(cons 'a '(b c))"), "(a b c)");
    assert_string_equal(interpret("(cons 'a (cons 'b (cons 'c '())))"), "(a b c)");
    assert_string_equal(interpret("(cons '(a b) '(c d))"), "((a b) c d)");
    assert_string_equal(interpret("(car (cons 'a '(b c)))"), "a");
    assert_string_equal(interpret("(cdr (cons 'a '(b c)))"), "(b c)");
    assert_string_equal(interpret("(cons (car '(a b c)) (cdr '(d e f)))"), "(a e f)");
    assert_string_equal(interpret("(cons (car '(a b c)) (cdr '(a b c)))"), "(a b c)");

    assert_string_equal(interpret("(cons 'a 'b)"), "(a . b)");
    assert_string_equal(interpret("(cdr '(a . b))"), "b");
    assert_string_equal(interpret("(cons 'a '(b . c))"), "(a b . c)");

    assert_string_equal(interpret("'(a . (b . (c . ())))"), "(a b c)");

    assert_string_equal(interpret("(list 'a 'b 'c)"), "(a b c)");
    assert_string_equal(interpret("(list 'a)"), "(a)");
    assert_string_equal(interpret("(list)"), "()");
}

static void test_section_2_4() {
    assert_string_equal(interpret("(let ((x 2)) (+ x 3))"), "5");
    assert_string_equal(interpret("(let ((y 3)) (+ 2 y))"), "5");
    assert_string_equal(interpret("(let ((x 2) (y 3)) (+ x y))"), "5");

    assert_string_equal(interpret("(+ (* 4 4) (* 4 4))"), "32");
    assert_string_equal(interpret("(let ((a (* 4 4))) (+ a a))"), "32");

    assert_string_equal(interpret("(let ([list1 '(a b c)] [list2 '(d e f)]) (cons (cons (car list1) (car list2)) (cons (car (cdr list1)) (car (cdr list2)))))"), "((a . d) b . e)");
    assert_string_equal(interpret("(let ([f +]) (f 2 3))"), "5");
    assert_string_equal(interpret("(let ([f +] [x 2]) (f x 3))"), "5");
    assert_string_equal(interpret("(let ([f +] [x 2] [y 3]) (f x y))"), "5");

    assert_string_equal(interpret("(let ([+ *]) (+ 2 3))"), "6");
    assert_string_equal(interpret("(+ 2 3)", "5"));

    assert_string_equal(interpret("(let ([a 4] [b -3]) (let ([a-squared (* a a)] [b-squared (* b b)]) (+ a-squared b-squared)))", "25"));

    assert_string_equal(interpret("(let ([x 1]) (let ([x (+ x 1)]) (+ x x)))", "4"));

    assert_string_equal(interpret("(let ([x 1]) (let ([new-x (+ x 1)]) (+ new-x new-x)))", "4"));
}

static void test_section_2_5() {
    assert_string_equal(interpret("(lambda (x) (+ x x))", "#<procedure>"));

    assert_string_equal(interpret("((lambda (x) (+ x x)) (* 3 4))", "24"));

    assert_string_equal(interpret("(let ([double (lambda (x) (+ x x))]) (list (double (* 3 4)) (double (/ 99 11)) (double (- 2 7))))", "(24 18 -10)"));

    assert_string_equal(interpret("(let ([double-cons (lambda (x) (cons x x))]) (double-cons 'a))", "(a . a)"));

    assert_string_equal(interpret("(let ([double-any (lambda (f x) (f x x))]) (list (double-any + 13) (double-any cons 'a)))", "(26 (a . a))"));

    assert_string_equal(interpret("(let ([x 'a]) (let ([f (lambda (y) (list x y))]) (f 'b)))", "(a b)"));

    assert_string_equal(interpret("(let ([f (let ([x 'sam]) (lambda (y z) (list x y z)))]) (f 'i 'am))", "(sam i am)"));

    assert_string_equal(interpret("(let ([f (let ([x 'sam]) (lambda (y z) (list x y z)))]) (let ([x 'not-sam]) (f 'i 'am)))", "(sam i am)"));

    assert_string_equal(interpret("(let ([f (lambda x x)]) (f 1 2 3 4))", "(1 2 3 4)"));

    assert_string_equal(interpret("(let ([f (lambda x x)]) (f))", "()"));

    assert_string_equal(interpret("(let ([g (lambda (x . y) (list x y))]) (g 1 2 3 4))", "(1 (2 3 4))"));

    assert_string_equal(interpret("(let ([h (lambda (x y . z) (list x y z))]) (h 'a 'b 'c 'd))", "(a b (c d))"));
}


